﻿using System;
using System.Windows.Browser;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Json;
using System.Text;
using System.Xml.Linq;
using SilverQuery;
using SilverPortal;
using SilverPortal.Util;

namespace SilverPortal.Modules
{
	public class StockModule : SilverPortalModule
	{
		private const int NumberHorizontalGridSegments = 4;
		private jQuery _chartContainer;

		protected override void Load()
		{
			base.RegisterCssResource("SilverPortal.Modules.StockModule.css");

			this.Container.addClass("stockReport");
			@q(TemplateHtml).appendTo(this.Container);
			_chartContainer = this.Container.find("div.chart");
			
			var symbolTextBox = @q("input[type=text]", this.Container);
			var getChartButton = @q("input[type=button]", this.Container);
			
			getChartButton.click(e =>
			{
				string symbol = symbolTextBox.val();
				if (symbol.Length > 0)
					LoadStockData(symbol);
				return false;
			});

			symbolTextBox.keydown(e =>
			{
				if (e.keyCode == 13)
					getChartButton.click();

				return false;
			}).focus(e => { e.target.Invoke("select"); return false;});

			if (base.State["stockSymbol"] != null)
			{
				symbolTextBox.val(State["stockSymbol"]);
				LoadStockData(State["stockSymbol"]);
			}
			else
				base.DoneLoading();
		}

		private void LoadStockData(string symbol)
		{
			base.State["stockSymbol"] = symbol;
			GenerateChart(symbol);
			DownloadQuote(symbol);
			DownloadHeadlines(symbol);
		}

		private void DownloadQuote(string symbol)
		{
			string url = String.Format("http://pipes.yahooapis.com/pipes/pipe.run?Symbols={0}&_id=75c2b9e6164d619c7581b7b510951a59&_render=json", symbol.ToUpper());
			Ajax.get(url, null, (data, error) =>
			{
				if (error != null)
					base.DisplayError("Error downloading quote data.", error);
				else
				{
					var json = JsonObject.Parse(data);
					var items = (JsonArray)json["value"]["items"];
					if (items.Count > 0)
						BindQuoteData(symbol, items[0]);
				}
			});
		}

		private void BindQuoteData(string symbol, JsonValue data)
		{
			var quoteDiv = @q("div.quote", this.Container);
				
			@q("span.symbol", quoteDiv).html(symbol.ToUpper() + ":");
			@q("span.price", quoteDiv).html(data["LastTrade"]);
			string change = data["Change"];
			var changeSpan = @q("span.change", quoteDiv).html(change);
			if (change.StartsWith("+"))
				changeSpan.removeClass("down").addClass("up");
			else
				changeSpan.removeClass("up").addClass("down");

			@q("span.date", quoteDiv).html(data["TradeDate"] + " " + data["TradeTime"]);
		}

		private void BindHeadlines(XDocument data)
		{
			var ul = @q("div.headlines ul", this.Container);
			var itemTemplate = ul.html();
			ul.empty();

			foreach (XElement item in data.Descendants("item"))
			{
				var li = @q(HeadlineTemplate);
				string title = item.Element("title").Value;
				string source = null;
				var i = title.LastIndexOf('(');
				if (i > 1)
				{
					source = title.Substring(i + 1).TrimEnd(')');
					if (source.StartsWith("at "))
						source = source.Substring(3);

					title = title.Substring(0, i - 1).Trim();
				}
				DateTime pubDate = DateTime.Parse(item.Element("pubDate").Value);

				@q("a", li).attr("href", item.Element("link").Value).html(title);
				@q("span.source", li).html(source + " - " + pubDate.ToString("ddd h:mm:tt"));
				ul.append(li);
			}
		}

		private void DownloadHeadlines(string symbol)
		{
			string url = RssUtil.ProxyRssUrl(String.Format("http://finance.yahoo.com/rss/headline?s={0}", symbol), 20);
			Ajax.get(url, null, (data, error) =>
			{
				if (error != null)
					base.DisplayError("Could not download headlines", error);
				else
				{
					var xml = XDocument.Parse(data);
					BindHeadlines(xml);
				}
			});
		}

		private void GenerateChart(string symbol)
		{
			DateTime startDate = DateTime.Today.AddMonths(-6);
			DateTime endDate = DateTime.Today;

			//http://pipes.yahooapis.com/pipes/pipe.run?_id4b367150c4b3b9f2276a1d09767741b4&_render=csv&url=
			Ajax.get("http://pipes.yahooapis.com/pipes/pipe.run",
				new map() { 
					{ "_id", "4b367150c4b3b9f2276a1d09767741b4"}, 
					{ "_render", "json" }, 
					{ "url", BuildCsvDownloadUrl(symbol, startDate, endDate) } 
				},
				(data, error) =>
				{
					if (error != null)
						base.DisplayError("Error loading stock price CSV.", error);
					else
					{
						// Clear out the last chart.
						_chartContainer.empty();

						base.Log.Info("Stock prices loaded for " + symbol);
						JsonObject json = (JsonObject)JsonObject.Parse(data);
						DataSeries series = LoadDataSeries(json, startDate, endDate);
						Uri chartUrl = GenerateGoogleChartUrl(series);
						@q("<img/>").attr("src", chartUrl.ToString()).appendTo(_chartContainer);
						base.DoneLoading();
					}
				});
		}

		private DataSeries LoadDataSeries(JsonObject data, DateTime startDate, DateTime endDate)
		{
			var items = (JsonArray)data["value"]["items"];
			var series = new DataSeries();
			double minValue = 0.0;
			double maxValue = 0.0;

			foreach (JsonValue v in items)
			{
				var date = DateTime.Parse(v["Date"]);
				var close = Double.Parse(v["Close"]);

			  if (minValue == 0.0 || close < minValue)
			    minValue = close;
			  if (close > maxValue)
			    maxValue = close;

				 series.Add(date, close);
			}
			series.MinValue = minValue;
			series.MaxValue = maxValue;
			series.StartDate = startDate;
			series.EndDate = endDate;

			CalculateXAxis(series);
			CalculateYAxisRange(series);

			return series;
		}

		private Uri GenerateGoogleChartUrl(DataSeries series)
		{
			StringBuilder sb = new StringBuilder();
			sb.Append("chs=" + _chartContainer.width() + "x" + _chartContainer.height());
			sb.Append("&cht=lc"); 
			sb.Append("&chd=s:");
			foreach (double price in series.ClosePrices)
			{
				sb.Append(SimpleEncodeValue(price, series.YAxisRangeStart, series.YAxisRangeEnd));
			}
			sb.Append("&chxt=x,r");
			sb.AppendFormat("&chxr=1,{0},{1},{2}", series.YAxisRangeStart, series.YAxisRangeEnd, series.YAxisStepSize);
						
			sb.AppendFormat("&chxl=0:|{0}", series.XAxisLabels.concat('|'));
			sb.AppendFormat("&chxp=0,{0}", series.XAxisLabelPositions.concat(','));
			sb.AppendFormat("&chg=0,{0}", 100 / NumberHorizontalGridSegments);
			sb.AppendFormat("&chls=2");

			UriBuilder url = new UriBuilder("http://chart.apis.google.com/chart");
			url.Query = sb.ToString();
			return url.Uri;
		}

		private void CalculateXAxis(DataSeries series)
		{
			var span = new TimeSpan(series.EndDate.Ticks - series.StartDate.Ticks).Days;
			// Find the first day of the month for each 
			DateTime dt;
			series.XAxisLabels = new List<string>();
			series.XAxisLabelPositions = new List<int>();
			for (int i = 0; i < span; i++)
			{
				dt = series.StartDate.AddDays(i);
				if (dt.Day == 1)
				{
					series.XAxisLabels.Add(dt.ToString("MMM"));
					series.XAxisLabelPositions.Add(Convert.ToInt32((double)i/(double)span * 100));
				}
			}
		}

		private void CalculateYAxisRange(DataSeries series)
		{
			int[] standardStepSizes = new[] { 2, 5, 10, 15, 20, 25, 40, 50, 100, 250, 500, 1000 };

			double delta = series.MaxValue - series.MinValue;
			double adjustedMinValue = Math.Round(series.MinValue - .10 * delta, 0);
			double adjustedMaxValue = Math.Round(series.MaxValue + .10 * delta, 0);

			double roughStepSize = (adjustedMaxValue - adjustedMinValue) / NumberHorizontalGridSegments;
			for (int i = 0; i < standardStepSizes.Length; i++)
			{
				if (roughStepSize < standardStepSizes[i])
				{
					series.YAxisStepSize = standardStepSizes[i];
					break;
				}
			}

			// Need to subtract from the adjustedMinValue until we reach a number that is equally divisible by the stepSize
			while (adjustedMinValue > 0 && (adjustedMinValue % series.YAxisStepSize) != 0.0)
				adjustedMinValue--;

			while ((adjustedMaxValue % series.YAxisStepSize) != 0.0)
				adjustedMaxValue++;

			series.YAxisRangeStart = Convert.ToInt32(adjustedMinValue);
			series.YAxisRangeEnd = Convert.ToInt32(adjustedMaxValue);
		}

		private string BuildCsvDownloadUrl(string symbol, DateTime startDate, DateTime endDate)
		{			
			StringBuilder url = new StringBuilder();
			url.Append("http://ichart.finance.yahoo.com/table.csv?");
			url.Append("s=" + symbol);
			url.Append("&a=" + (startDate.Month - 1));
			url.Append("&b=" + (startDate.Day));
			url.Append("&c=" + startDate.Year);
			url.Append("&d=" + (endDate.Month - 1));
			url.Append("&e=" + endDate.Day);
			url.Append("&f=" + endDate.Year);
			url.Append("&g=w");

			return url.ToString();
		}

		private string SimpleEncodeValue(double value, int minValue, int maxValue)
		{
			string encodingKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
						
			double percent = (double)(value - minValue)/(double)(maxValue - minValue);
			int keyIndex = Convert.ToInt32(percent * encodingKey.Length)-1;

			//int keyIndex = Convert.ToInt32(Math.Round((encodingKey.Length - 1) * (double)(value - minValue)/(double)(maxValue - minValue), 0));
			return encodingKey[keyIndex].ToString();
		}

		private class DataSeries
		{
			public DataSeries()
			{
				this.Dates = new List<DateTime>();
				this.ClosePrices = new List<Double>();
			}

			public IList<DateTime> Dates { get; private set; }
			public IList<Double> ClosePrices { get; private set; }
			public DateTime StartDate { get; set; }
			public DateTime EndDate { get; set; }
			public Double MinValue { get; set; }
			public Double MaxValue { get; set; }
			public IList<string> XAxisLabels { get; set; }
			public IList<int> XAxisLabelPositions { get; set;}
			public int YAxisRangeStart { get; set; }
			public int YAxisRangeEnd { get; set; }
			public int YAxisStepSize { get; set; }

			public void Add(DateTime date, double close)
			{
				Dates.Insert(0, date);
				ClosePrices.Insert(0, close);
			}
		}

		private const string TemplateHtml = @"
<div>
	<div class=""header"">
		<span>Symbol:</span>
		<input type=""text"" /><input type=""button"" value=""Get Chart"" />
	</div>
	<div class=""main"">
		<div class=""quote""><span class=""symbol"" /><span class=""price"" /><span class=""change"" /><span class=""date"" /></div>
		<div class=""chart"" />
		<div class=""headlines"">
			<h3>Headlines</h3>
			<ul />
		</div>
	</div>
</div>";

		private const string HeadlineTemplate = @"
<li>
	<a target=""_blank"" />
  <span class=""source""/>
</li>";
	}
}
